Skip to content

feat(core,html,react): vimeo provider with configurable embed params#1538

Draft
ronald-urbina wants to merge 9 commits into
mainfrom
feat/vimeo-provider
Draft

feat(core,html,react): vimeo provider with configurable embed params#1538
ronald-urbina wants to merge 9 commits into
mainfrom
feat/vimeo-provider

Conversation

@ronald-urbina

@ronald-urbina ronald-urbina commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Closes #1435

Summary

Implements the Vimeo provider across core, html, and react packages.

  • VimeoMedia (core/dom) — Headless media engine adapter backed by @vimeo/player. Implements MediaEngineHost, MediaPictureInPictureCapability, and MediaTextTrackCapability. Exposes the full Vimeo embed parameter surface (dnt, byline, portrait, title, color, autopause, background, loop, muted, responsive, speed, texttrack, transparent, quality, playsinline, controls). Defaults dnt: true for GDPR compliance out of the box.

  • VimeoVideo (html) — Custom element wrapping VimeoMedia. All embed params are reflected as HTML attributes. Ships a CDN-ready define entry and registers with customElements via define/media/vimeo-video.ts.

  • VimeoVideo (react)forwardRef component. Accepts all embed params as props; syncs them to the underlying VimeoMedia instance via useSyncProps. Renders a div container that VimeoMedia targets (not a native <video> element).

  • PiP — Safari-only via WebKit's cross-origin iframe mechanism; flagged via isPipCapable on the adapter.

  • Sandbox — Vimeo source wired into the dev playground for manual testing.

  • Skins — CSS updated so the Vimeo iframe fills the player container consistently in both default and minimal themes.

Test plan

  • pnpm -F @videojs/core test src/dom/media/vimeo — unit tests pass
  • pnpm typecheck — no type errors
  • pnpm -F sandbox dev — load a Vimeo source in the sandbox, verify play/pause/seek/mute/volume/duration/ended events
  • Verify dnt=true is sent in the embed URL by default (check Network tab iframe src)
  • Toggle dnt, byline, portrait, title attributes in DevTools and confirm the player reinitializes with updated params
  • Verify PiP is gated to Safari (button absent on Chrome/Firefox)

Note

High Risk
Adds a new cross-origin iframe-backed media engine (VimeoMedia) plus updates Picture-in-Picture availability/exit logic, which can affect core playback/event behavior across players. Integration relies on the external @vimeo/player SDK and new state/event mappings, increasing surface area for regressions.

Overview
Adds first-class Vimeo playback support by introducing VimeoMedia in @videojs/core (backed by @vimeo/player) with event/state bridging, configurable embed parameters, text track support, and Safari-only PiP signaling.

Exposes the provider via new HTML custom element vimeo-video (including CDN define entry) and a new React VimeoVideo component that syncs props to the underlying engine.

Updates PiP detection/exit to consider media-specific capability (isPipCapable and provider-level exitPictureInPicture), adds unit tests for both PiP gating and the new Vimeo engine, and extends the sandbox (new vimeo-video preset, Vimeo sources, and HTML/React templates) plus skin CSS/Tailwind tweaks so Vimeo embeds size correctly.

Reviewed by Cursor Bugbot for commit 14ac63f. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented May 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview, Comment May 14, 2026 7:08pm

Request Review

@netlify

netlify Bot commented May 12, 2026

Copy link
Copy Markdown

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 14ac63f
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/6a061da72a01cd0008a06dde
😎 Deploy Preview https://deploy-preview-1538--vjs10-site.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

github-actions Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Size Report

🎨 @videojs/html

Path Base PR Diff %
/media/vimeo-video 10.06 kB 🆕
Presets (7)
Entry Size
/video (default) 32.86 kB
/video (default + hls) 166.55 kB
/video (minimal) 32.52 kB
/video (minimal + hls) 166.11 kB
/audio (default) 27.41 kB
/audio (minimal) 24.52 kB
/background 4.16 kB
Media (9)
Entry Size
/media/background-video 1.04 kB
/media/container 1.72 kB
/media/dash-video 236.58 kB
/media/hls-video 134.87 kB
/media/mux-audio 160.89 kB
/media/mux-video 161.01 kB
/media/native-hls-video 4.62 kB
/media/simple-hls-video 16.28 kB
/media/vimeo-video 10.06 kB
Players (5)
Entry Size
/video/player 7.02 kB
/audio/player 5.12 kB
/background/player 3.86 kB
/live-video/player 7.04 kB
/live-audio/player 5.14 kB
Skins (30)
Entry Type Size
/video/minimal-skin.css css 4.49 kB
/video/skin.css css 4.48 kB
/video/minimal-skin js 32.47 kB
/video/minimal-skin.tailwind js 32.86 kB
/video/skin js 32.87 kB
/video/skin.tailwind js 33.26 kB
/audio/minimal-skin.css css 2.69 kB
/audio/skin.css css 2.66 kB
/audio/minimal-skin js 24.50 kB
/audio/minimal-skin.tailwind js 24.66 kB
/audio/skin js 27.37 kB
/audio/skin.tailwind js 27.57 kB
/background/skin.css css 133 B
/background/skin js 1.16 kB
/live-video/minimal-skin.css css 4.49 kB
/live-video/skin.css css 4.48 kB
/live-video/minimal-skin js 32.21 kB
/live-video/minimal-skin.tailwind js 32.48 kB
/live-video/skin js 32.28 kB
/live-video/skin.tailwind js 32.54 kB
/live-audio/minimal-skin.css css 2.69 kB
/live-audio/skin.css css 2.66 kB
/live-audio/minimal-skin js 24.32 kB
/live-audio/minimal-skin.tailwind js 24.29 kB
/live-audio/skin js 26.90 kB
/live-audio/skin.tailwind js 26.92 kB
/global.css css 185 B
/shared.css css 88 B
/tailwind.css css 228 B
/skin-element js 1.37 kB
UI Components (34)
Entry Size
/ui/alert-dialog 693 B
/ui/alert-dialog-close 313 B
/ui/alert-dialog-description 275 B
/ui/alert-dialog-title 265 B
/ui/buffering-indicator 2.05 kB
/ui/captions-button 2.00 kB
/ui/cast-button 2.04 kB
/ui/compounds 5.18 kB
/ui/controls 1.94 kB
/ui/error-dialog 2.44 kB
/ui/fullscreen-button 1.99 kB
/ui/hotkey 2.67 kB
/ui/menu 1.96 kB
/ui/mute-button 2.04 kB
/ui/pip-button 2.05 kB
/ui/play-button 2.03 kB
/ui/playback-rate-button 2.14 kB
/ui/popover 1.47 kB
/ui/poster 1.80 kB
/ui/seek-button 2.02 kB
/ui/seek-indicator 2.65 kB
/ui/seek-indicator-value 243 B
/ui/slider 1.18 kB
/ui/status-announcer 2.38 kB
/ui/status-indicator 2.42 kB
/ui/status-indicator-value 149 B
/ui/thumbnail 2.48 kB
/ui/time 1.93 kB
/ui/time-slider 3.06 kB
/ui/tooltip 1.63 kB
/ui/volume-indicator 2.67 kB
/ui/volume-indicator-fill 280 B
/ui/volume-indicator-value 280 B
/ui/volume-slider 3.52 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react

Path Base PR Diff %
/media/vimeo-video 9.96 kB 🆕
Presets (7)
Entry Size
/video (default) 26.38 kB
/video (default + hls) 158.77 kB
/video (minimal) 26.38 kB
/video (minimal + hls) 158.90 kB
/audio (default) 19.22 kB
/audio (minimal) 17.68 kB
/background 756 B
Media (8)
Entry Size
/media/background-video 575 B
/media/dash-video 235.21 kB
/media/hls-video 133.39 kB
/media/mux-audio 159.70 kB
/media/mux-video 159.68 kB
/media/native-hls-video 3.13 kB
/media/simple-hls-video 14.92 kB
/media/vimeo-video 9.96 kB
Skins (27)
Entry Type Size
/tailwind.css css 228 B
/video/minimal-skin.css css 4.41 kB
/video/skin.css css 4.39 kB
/video/minimal-skin js 26.32 kB
/video/minimal-skin.tailwind js 30.91 kB
/video/skin js 26.29 kB
/video/skin.tailwind js 30.84 kB
/audio/minimal-skin.css css 2.56 kB
/audio/skin.css css 2.51 kB
/audio/minimal-skin js 17.62 kB
/audio/minimal-skin.tailwind js 20.25 kB
/audio/skin js 19.14 kB
/audio/skin.tailwind js 20.27 kB
/background/skin.css css 90 B
/background/skin js 272 B
/live-video/minimal-skin.css css 4.41 kB
/live-video/skin.css css 4.39 kB
/live-video/minimal-skin js 22.95 kB
/live-video/minimal-skin.tailwind js 27.39 kB
/live-video/skin js 23.02 kB
/live-video/skin.tailwind js 27.51 kB
/live-audio/minimal-skin.css css 2.56 kB
/live-audio/skin.css css 2.51 kB
/live-audio/minimal-skin js 16.31 kB
/live-audio/minimal-skin.tailwind js 18.72 kB
/live-audio/skin js 17.86 kB
/live-audio/skin.tailwind js 18.86 kB
UI Components (27)
Entry Size
/ui/alert-dialog 1.10 kB
/ui/buffering-indicator 1.87 kB
/ui/captions-button 2.14 kB
/ui/cast-button 2.11 kB
/ui/controls 1.85 kB
/ui/error-dialog 2.28 kB
/ui/fullscreen-button 2.10 kB
/ui/gesture 1.36 kB
/ui/hotkey 1.42 kB
/ui/live-button 2.06 kB
/ui/menu 3.87 kB
/ui/mute-button 2.05 kB
/ui/pip-button 2.06 kB
/ui/play-button 2.11 kB
/ui/playback-rate-button 2.16 kB
/ui/popover 1.91 kB
/ui/poster 1.79 kB
/ui/seek-button 2.09 kB
/ui/seek-indicator 1.88 kB
/ui/slider 3.19 kB
/ui/status-indicator 1.99 kB
/ui/thumbnail 1.97 kB
/ui/time 1.96 kB
/ui/time-slider 2.96 kB
/ui/tooltip 2.33 kB
/ui/volume-indicator 1.95 kB
/ui/volume-slider 2.30 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core

Path Base PR Diff %
/dom/media/vimeo 9.24 kB 🆕
Entries (10)
Entry Size
. 7.26 kB
/dom 14.58 kB
/dom/media/custom-media-element 1.90 kB
/dom/media/dash 234.36 kB
/dom/media/google-cast 4.07 kB
/dom/media/hls 132.98 kB
/dom/media/mux 159.10 kB
/dom/media/native-hls 2.52 kB
/dom/media/simple-hls 14.31 kB
/dom/media/vimeo 9.24 kB
🏷️ @videojs/element — no changes
Entries (2)
Entry Size
. 996 B
/context 943 B
📦 @videojs/store — no changes
Entries (3)
Entry Size
. 1.39 kB
/html 695 B
/react 360 B
🔧 @videojs/utils — no changes
Entries (10)
Entry Size
/array 104 B
/dom 1.92 kB
/events 319 B
/function 327 B
/object 275 B
/predicate 265 B
/string 192 B
/style 190 B
/time 478 B
/number 158 B
📦 @videojs/spf — no changes
Entries (3)
Entry Size
. 4.45 kB
/dom 7.69 kB
/hls 13.65 kB

ℹ️ How to interpret

All sizes are standalone totals (minified + brotli).

Icon Meaning
No change
🔺 Increased ≤ 10%
🔴 Increased > 10%
🔽 Decreased
🆕 New (no baseline)

Run pnpm size locally to check current sizes.

@ronald-urbina ronald-urbina marked this pull request as ready for review May 13, 2026 19:25
Comment thread packages/core/src/dom/presentation/pip.ts Outdated
Comment thread apps/sandbox/app/shared/sources.ts
Comment thread packages/core/src/dom/media/vimeo/tests/vimeo-media.test.ts
Comment thread packages/core/src/dom/media/vimeo/index.ts
Comment thread packages/core/src/dom/media/vimeo/index.ts
Comment thread packages/core/src/dom/media/vimeo/index.ts
Comment thread packages/core/src/dom/media/vimeo/index.ts
Comment thread packages/core/src/dom/media/vimeo/index.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 14ac63f. Configure here.


set volume(value: number) {
this.#player?.setVolume(value);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Volume setter doesn't update cached value immediately

Medium Severity

The volume setter calls setVolume on the SDK but doesn't update #volume locally, so media.volume returns a stale value until the async volumechange event fires. The playbackRate setter has the same issue. This is inconsistent with other setters like muted, loop, autopause, color, and quality, which all update their local cache immediately. It also differs from HTMLVideoElement behavior where the getter reflects the set value synchronously.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 14ac63f. Configure here.

iframe?.focus();
};
globalThis.document?.addEventListener('click', focusIframe, { capture: true });
this.#activationCleanup = () => globalThis.document?.removeEventListener('click', focusIframe, { capture: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global click listener steals focus on every click

Medium Severity

The focusIframe handler added in attach() listens for every click on document in the capture phase and calls iframe.focus(), which changes document.activeElement to the iframe. This steals focus from whatever the user actually clicked on — interactive elements like inputs get refocused by the browser's default behavior (causing a focus flicker with extra focusin/focusout events), while non-standard focusable elements may permanently lose focus. The listener also runs on all browsers even though its purpose (PiP user activation) is Safari-only per isPipCapable.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 14ac63f. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Feature: Vimeo Provider

1 participant